package com.pocket.insight.entity;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.pocket.insight.base.constant.FileConst;
import com.pocket.insight.base.utils.JSONUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 描述：
 *
 * @author shuize
 * @create 2024/11/29
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dataset {

    private Long              id;
    private Long              appId;
    private String            title;
    private Options           options;
    private List<Object>      where;
    private Boolean           canPushToMemory;
    private List<Long>        appendDatasets;
    private List<ObjectGroup> fieldGroups;
    private List<ObjectGroup> metricGroups;
    private ME                me;
    private Boolean           enableUpstreamAcl;


    public static List<Field> fetchVisibleFields(List<Field> schema) {
        return CollectionUtils.emptyIfNull(schema).stream().filter(Field::getVisible).collect(Collectors.toList());
    }

    public Dataset(Options options) {
        this.options = options;
    }

    public Map<String, Field> fetchSchemaMap() {
        return this.options == null ? null : this.options.schemaMap;
    }

    public Map<String, Metric> fetchMetricMap() {
        return this.options == null ? null : this.options.metricMap;
    }


    public List<Field> getSchema() {
        return this.options == null ? null : this.options.getSchema();
    }

    public void setSchema(List<Field> schema) {
        if (this.options == null) {
            this.options = new Options();
        }
        this.options.setSchema(schema);
    }


    public List<Object> getWhere() {
        return this.where;
    }

    public Boolean getCanPushToMemory() {
        return this.canPushToMemory;
    }

    public List<Long> getAppendDatasets() {
        return this.appendDatasets;
    }

    public static <T> List<T> copyList(List<T> from) {
        return from != null ? new ArrayList<>(from) : null;
    }

    public Dataset partialDeepCopy() {
        try {
            Dataset copy = (Dataset) clone();
            if (this.options != null) {
                copy.setOptions(this.options.partialDeepCopy());
            }
            copy.setFieldGroups(copyList(getFieldGroups()));
            copy.setMetricGroups(copyList(getMetricGroups()));
            return copy;
        } catch (CloneNotSupportedException cloneNotSupportedException) {
            return JSONUtil.deepCopy(this);
        }
    }


    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Options implements Serializable, Cloneable {
        private Map<String, Field>    schemaMap = new LinkedHashMap<>();
        private Map<String, Metric>   metricMap = new LinkedHashMap();
        private Long                  connectionId;
        private String                connectionTitle;
        private List<String>          path;
        private String                table;
        private String                customSql;
        private List<Object>          where;
        private DatasetTypeEnum       type;
        private String origin;
        private Dataset.ExtendOptions extendOptions;
        private Long                  rowCount  = 0L;
        private UnionOptions          unionOptions;
        private List<Long>            appendDatasets;
        private ME                    me;
        private Long                  rootDatasetId;
        private ReferenceOptions      referenceOptions;
        private List<JoinOpt>         joinOpts;


        public Dataset.ExtendOptions getExtendOptions() {
            return this.extendOptions;
        }

        @JsonIgnore
        public boolean isFusion() {
            return (this.type == DatasetTypeEnum.fusion);
        }

        @JsonIgnore
        public boolean isUnion() {
            return (this.type == DatasetTypeEnum.union);
        }

        @JsonIgnore
        public boolean isAggregate() {
            return (this.type == DatasetTypeEnum.aggregate);
        }

        @JsonIgnore
        public boolean isPivot() {
            return (this.type == DatasetTypeEnum.pivot);
        }

        @JsonIgnore
        public boolean isUnpivot() {
            return (this.type == DatasetTypeEnum.unpivot);
        }

        @JsonIgnore
        public boolean isConnection() {
            return (this.type == DatasetTypeEnum.connection);
        }

        @JsonIgnore
        public Boolean isExtendDataset() {
            return Boolean.valueOf((getExtendOptions() != null));
        }

        public List<Field> getSchema() {
            return new ArrayList(this.schemaMap.values());
        }

        public DatasetTypeEnum fetchType() {
            return isExtendDataset().booleanValue() ? DatasetTypeEnum.extend : this.type;
        }

        @JsonIgnore
        public Boolean isReference() {
            return this.getReferenceOptions() != null;
        }

        public String getOrigin() {
            return this.origin;
        }

        @JsonGetter("isFile")
        public boolean isFile() {
            return FileConst.FILE_ORIGINS.contains(this.getOrigin());
        }

        public void setSchema(List<Dataset.Field> schema) {
            this.schemaMap.clear();
            if (CollectionUtils.isNotEmpty(schema)) {
                for (int i = 0; i < schema.size(); i++) {
                    if (this.schemaMap.containsKey((schema.get(i)).getFieldName()) && (schema.get(i)).getDbFieldName() == null) {
                        (schema.get(i)).setDbFieldName((schema.get(i)).getFieldName());
                        (schema.get(i)).setFieldName((schema.get(i)).getFieldName() + "_" + (schema.get(i)).getFieldName());
                    }
                    this.schemaMap.put((schema.get(i)).getFieldName(), schema.get(i));
                }
            }
        }

        public List<String> fetchTableFullPath() {
            List<String> tableFullPath = new ArrayList<>();
            if (this.path != null) {
                tableFullPath.addAll(this.path);
            }
            if (StringUtils.isNotBlank(this.table)) {
                tableFullPath.add(this.table);
            }
            return tableFullPath;
        }

        public Options partialDeepCopy() {
            try {
                Options copy = (Options) clone();
                copy.schemaMap = new LinkedHashMap<>();
                copy.setSchema(DatasetField.partialDeepCopyList(getSchema()));
                copy.metricMap = new LinkedHashMap<>();
                copy.setWhere(Dataset.copyList(getWhere()));
                copy.setAppendDatasets(Dataset.copyList(getAppendDatasets()));
                copy.setMe((getMe() == null) ? null : JSONUtil.deepCopy(getMe()));
                return copy;
            } catch (CloneNotSupportedException cloneNotSupportedException) {
                return JSONUtil.deepCopy(this);
            }
        }

        public enum DatasetTypeEnum {
            connection,
            internal_storage,
            fusion,
            union,
            aggregate,
            extend,
            reference,
            pivot,
            unpivot;
        }

        @Data
        @JsonIgnoreProperties(ignoreUnknown = true)
        public static class UnionOptions implements Serializable {
            private boolean                                append;
            private List<Dataset.Options.UnionSchemaField> unionSchema   = new ArrayList<>();
            private List<Dataset.Options.UnionSrcDataset>  fieldsMapping = new ArrayList<>();
            private Long                                   baseDatasetId;

            public List<UnionSrcDataset> fetchFieldsMapping() {
                List<UnionSrcDataset> mapping = this.getFieldsMapping().stream().map(UnionSrcDataset::duplicate).collect(Collectors.toList());
                if (CollectionUtils.isNotEmpty(mapping) && this.append) {
                    mapping.get(0).setDatasetId(this.getBaseDatasetId());
                }
                return mapping;
            }
        }

        @Data
        @JsonIgnoreProperties(ignoreUnknown = true)
        public static class UnionSchemaField {
            private String        label;
            private Type.TypeName type;
        }

        @Data
        @JsonIgnoreProperties(ignoreUnknown = true)
        public static class UnionSrcDataset implements Serializable {
            private Long         datasetId;
            private List<String> fieldNames = new ArrayList<>();
            private List<Object> where;
            public UnionSrcDataset duplicate() {
                UnionSrcDataset unionSrcDataset = new UnionSrcDataset();
                unionSrcDataset.setDatasetId(this.getDatasetId());
                unionSrcDataset.setFieldNames(new ArrayList(this.fieldNames));
                if (this.where != null) {
                    unionSrcDataset.setWhere(new ArrayList(this.where));
                }
                return unionSrcDataset;
            }
        }

        @Data
        public static class JoinOpt {
            private Long                  joinDatasetId;
            private String                joinType;
            private List<List<JoinField>> joinOn;
            private String                joinDatasetName;
            private Integer               datasetIndex;
            private String                uid;
        }

        @Data
        public static class JoinField {
            private String        field;
            private Type.TypeName type;
            private Long          datasetId;
            private String        uid;
        }
    }

    @Data
    public static class Field extends DatasetField implements Serializable, AsField {
        public String getOriginName() {
            return super.getGeneratedOptions().getOriginName();
        }

        public String getOriginLabel() {
            return super.getGeneratedOptions().getOriginLabel();
        }

        @Override
        public String getLabel() {
            return super.getOptions().getLabel();
        }

        public void setLabel(String label) {
            if (StringUtils.isBlank(label)) {
                label = null;
            }
            super.getOptions().setLabel(label);
        }

        public void setComment(String comment) {
            super.getGeneratedOptions().setComment(comment);
        }

        public String getComment() {
            return super.getGeneratedOptions().getComment();
        }

        @Override
        public String getUserComment() {
            return super.getOptions().getUserComment();
        }

        public void setUserComment(String userComment) {
            super.getOptions().setUserComment(userComment);
        }

        public Type.TypeName getOriginType() {
            return super.getGeneratedOptions().getOriginType();
        }

        @Override
        public Field partialDeepCopy() {
            return null;
        }

        public Boolean getVisible() {
            return super.getOptions().getVisible();
        }

        public String getDbFieldName() {
            return super.getOptions().getDbFieldName();
        }

        public void setType(Type.TypeName type) {
            super.getOptions().setType(type);
        }

        @JsonIgnore
        public boolean isUserAdded() {
            return this.getExpr() != null;
        }

        public ME getExpr() {
            DatasetField.Options options = super.getOptions();
            return options == null ? null : options.getExpr();
        }

        public String fetchDbFieldName() {
            String dbFieldNameTmp = super.getOptions().getDbFieldName();
            return StringUtils.isBlank(dbFieldNameTmp) ? this.getFieldName() : dbFieldNameTmp;
        }

        public static Field fromDatasetFieldKeepTypeOnly(DatasetField datasetField) {
            Field field = new Field();
            field.setDatasetId(datasetField.getDatasetId());
            field.setFieldName(datasetField.getFieldName());
            field.setId(datasetField.getId());
            field.setOptions(datasetField.getOptions().partialDeepCopy());
            field.getOptions().setExpr(null);
            field.getOptions().setDbFieldName(null);
            field.setGeneratedOptions(datasetField.getGeneratedOptions().partialDeepCopy());
            if (datasetField.getOptions().getType() != null && field.getGeneratedOptions().getOriginType() != null && field.getType() != field.getGeneratedOptions().getOriginType().type().getDisplayTypeName()) {
                field.getGeneratedOptions().setOriginType(datasetField.getOptions().getType());
                field.getGeneratedOptions().setNativeType(datasetField.getOptions().getType().toString());
                field.getGeneratedOptions().setSqlType(null);
            }

            field.getGeneratedOptions().setIsReal((Boolean) null);
            field.setAppId(datasetField.getAppId());
            field.setExtendAppId(datasetField.getExtendAppId());
            field.setTags(datasetField.getTags());
            field.setPurpose(datasetField.getPurpose());
            return field;
        }

        @Override
        @JsonProperty
        public void setGeneratedOptions(DatasetField.GeneratedOptions options) {
            super.setGeneratedOptions(options);
        }

        public Type.TypeName getType() {
            Type.TypeName type = this.fetchType();
            if (type != null) {
                type = type.type().getDisplayTypeName();
            }
            return type;
        }

        public Type.TypeName fetchType() {
            return this.fetchType(null);
        }

        public Type.TypeName fetchType(Type.TypeName requiredType) {
            Type.TypeName type = requiredType == null ? super.getOptions().getType() : requiredType;
            Type.TypeName originType = this.getOriginType();
            if (type == null) {
                return originType;
            } else {
                if (type == Type.TypeName.number || type == Type.TypeName.date) {
                    Type.TypeName displayType = type.type().getDisplayTypeName();
                    if (originType != null && originType.type().getDisplayTypeName() == displayType) {
                        return originType;
                    }
                    Iterator var5 = super.getGeneratedOptions().getSuggestedTypes().iterator();

                    while (var5.hasNext()) {
                        Type.FieldType fieldType = (Type.FieldType) var5.next();
                        if (fieldType.type.type().getDisplayTypeName() == displayType) {
                            return fieldType.type;
                        }
                    }
                    if (type == Type.TypeName.date && StringUtils.contains(this.getConfig().fetchDateFormat(), "HH")) {
                        type = Type.TypeName.time;
                    }
                }
                return type;
            }
        }

        public Type.TypeConfig getConfig() {
            JSONObject config = new JSONObject();
            super.getGeneratedOptions().getSuggestedTypes().forEach((t) -> {
                if (t.config != null) {
                    config.putAll(JSON.parseObject(JSON.toJSONString(t.config)));
                }
            });
            config.putAll(JSON.parseObject(JSON.toJSONString(super.getOptions().getConfig())));
            Type.TypeConfig typeConfig = JSON.toJavaObject(config, Type.TypeConfig.class);
            if (StringUtils.isEmpty(typeConfig.dateFormat) && typeConfig.formatter != null && typeConfig.formatter.containsKey("dateFormat")) {
                typeConfig.dateFormat = typeConfig.formatter.getString("dateFormat");
            }
            return typeConfig;
        }

        public void setDbFieldName(String dbFieldName) {
            super.getOptions().setDbFieldName(dbFieldName);
        }

        public boolean hasUserDateFormat() {
            Type.TypeConfig typeConfig = super.getOptions().getConfig();
            return (typeConfig.fetchDateFormat() != null);
        }
    }

    public interface AsField {
        String getFieldName();

        String getLabel();

        String getUserComment();

        default String fetchLabelOrFieldName() {
            return StringUtils.isBlank(this.getLabel()) ? this.getFieldName() : this.getLabel();
        }

    }

    @Data
    public static class Metric extends DatasetField implements Serializable, AsField {
        private String        label;
        private MetricME      expr;
        private Type.TypeName type;
        private Long          hsVersion;
        private Long          groupId;

        @Override
        public String getLabel() {
            return this.getLabel();
        }

        @Override
        public String getUserComment() {
            return this.getOptions().getUserComment();
        }
    }


    public static class MetricME extends ME {
        public Boolean isConstant;
        public Boolean allFieldsFromBaseDataset;

        public static MetricME fromME(ME expr) {
            return JSON.toJavaObject(JSON.parseObject(JSON.toJSONString(expr)), MetricME.class);
        }
    }

    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class ExtendOptions implements Serializable {
        private Long                            baseId;
        private Long                            upstreamId;
        private Dataset.JOIN_TYPE               joinType;
        private ME                              joinExpr;
        private Dataset.Options.DatasetTypeEnum baseType;
        private String                          baseOrigin;
        private Dataset.CARDINALITY             cardinality;
        private Boolean                         alwaysJoin;
        private Integer                         totalMetricsCount;
        private ME                              filter;
    }

    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class ReferenceOptions implements Serializable {
        private Long                    sourceAppId;
        private String                  sourceAppTitle;
        private List<String>            sourceAppPathName;
        private List<Long>              sourceAppPathId;
        private Long                    sourceDatasetId;
        private String                  sourceDatasetTitle;
        private Options.DatasetTypeEnum sourceType;
        private String                  sourceOrigin;
    }

    public enum CARDINALITY {
        ONE_TO_ONE,
        ONE_TO_MANY,
        MANY_TO_ONE,
        MANY_TO_MANY;
    }

    public enum JOIN_TYPE {
        LEFT_JOIN,
        RIGHT_JOIN,
        INNER_JOIN,
        FULL_JOIN,
        CROSS_JOIN,
        LEFT_LOOP_JOIN;
    }

    public enum STATUS {
        NONE,
        SUCCESS,
        EMPTY,
        ;
        private static STATUS[] VALUES = values();

        public static STATUS fromOrdinal(Integer ordinal) {
            return ordinal != null && ordinal < VALUES.length ? VALUES[ordinal] : null;
        }
    }
}
